'''
Created on 18.11.2012

@author: B. Wilbertz
'''

import threading, logging, time, gps
import RPi.GPIO as GPIO
from PyCoiGlobals import pins,blinkTime,crossModeBlinkInterval,pumpTime,pumpCoolDownTime,rainSignalAlpha,rainSignalEntryLimitDry,rainSignalEntryLimitRain,bounceInterval,GPIOInterval,statFile,sleepTimeTemperature
from Utilities import xstr
        

class BlinkThread(threading.Thread):
    '''
    Thread for periodically blinking the crossMode led
    '''

    def __init__(self, state):
        '''
        Constructor
        '''
        threading.Thread.__init__(self)
        self.state = state
        logging.info("Creating BlinkThread")            

    def run(self):
        logging.info("Starting BlinkThread...")
            
        try:
            for i in range(3):
                GPIO.output(pins.crossModeLED, GPIO.HIGH)
                GPIO.output(pins.rainModeLED, GPIO.HIGH)
                time.sleep(0.333)
                GPIO.output(pins.crossModeLED, GPIO.LOW)
                GPIO.output(pins.rainModeLED, GPIO.LOW)
                time.sleep(0.333)
        except Exception,e: 
            logging.critical('Exception while accessing status LED pins:' + str(e))
        finally:
            GPIO.output(pins.crossModeLED, GPIO.LOW)
            GPIO.output(pins.rainModeLED, GPIO.LOW)
            
        logging.info("Initial status blinking done.")
        
        while (self.state.OK):
            if self.state.crossModeEvent.is_set():
                self.state.crossModeEvent.clear()
            
            if self.state.crossMode == False:
                logging.debug("Blinking in normal mode")
                GPIO.output(pins.crossModeLED, GPIO.HIGH)
                time.sleep(blinkTime)
                GPIO.output(pins.crossModeLED, GPIO.LOW)
                self.state.crossModeEvent.wait(crossModeBlinkInterval - blinkTime)
            else:
                logging.debug("<<Blinking>> in cross mode")
                GPIO.output(pins.crossModeLED, GPIO.HIGH)
                self.state.crossModeEvent.wait(crossModeBlinkInterval)
    
        logging.warning("Exiting BlinkThread")
    
class PumpThread(threading.Thread):
    '''
    Thread for switching the pump on and off
    according to state.nPumpStrokes.
    '''

    def __init__(self, state):
        '''
        Constructor
        '''
        threading.Thread.__init__(self)
        self.state = state
        logging.info("Creating PumpThread")
        
    def run(self):
        logging.info("Starting PumpThread")
        
        while (self.state.OK):
            self.state.pumpEvent.wait()
            
            nStrokes = self.state.nPumpStrokes
            for i in range(nStrokes):
                logging.info("Pumping now!")
                try:
                    GPIO.output(pins.pump, GPIO.HIGH)
                    time.sleep(pumpTime)
                except Exception,e: 
                    logging.critical('Exception while pumping:' + str(e))
                finally:
                    GPIO.output(pins.pump, GPIO.LOW)
    
                # we wait a bit to allow the pump to cool down
                time.sleep(pumpCoolDownTime)
                
            self.state.pumpEvent.clear()
            
        logging.warning("Exiting PumpThread")
  


def setupGPS():
    '''
    Opens a gps sessions and waits until we got
    a gps fix and time from gpsd.
    '''
    logging.info('Initializing GPS...')
    session = gps.gps(mode=gps.WATCH_ENABLE)
    gpsTime = None
    
    try:
        while gpsTime is None:
            report = session.next()
            if report.get("class") == "TPV":
                try:
                    gpsTime = report.get("time")
                except:
                    logging.error("Error within report: " + str(report))
  
        logging.info('GPS is fine and has utc time: ' + xstr(gpsTime))            
    except StopIteration:
        logging.fatal("GPSD has terminated")
        
    return session

  
class ControlThread(threading.Thread):
    '''
    The main thread, which
    - polls the gps daemon
    - updates the gps state
    - updates the distance in the operating state
    - updates state.nPumpStrokes
    - updates the display
    - triggers the pump
    
    If the main loop of this thread dies, it terminates the whole application. 
    '''
    
    def __init__(self, state, displayUpdater, app):
        threading.Thread.__init__(self)
        logging.info("Creating ControlThread")
        self.state = state
        self.displayUpdater = displayUpdater
        self.app = app
        

    def run(self):
        self.displayUpdater.importState(self.state)
        
        # ini gps
        try:
            gpsSession = setupGPS()
        except Exception,e:
            self.state.OK = False
            logging.fatal("Control Thread: Error initializing GPS: " + str(e))
            self.app.exit(1)
            return
        
        self.state.setInitialGPSTime(gpsSession.utc)
        self.displayUpdater.importState(self.state)
            
        # starting main loop
        try:
            while True:
                report = gpsSession.next()
                
                # Do more stuff
                if report.get("class") == "TPV":
                    try:
                        self.state.gpsState.updateFromTPV(report)
                        
                        # importState distance
                        self.state.updateDistance()
                        
                        # update pump conditions
                        self.state.updatePumpState()
                        
                        # sent new state to GUI
                        self.displayUpdater.importState(self.state)
                        self.state.displayConnection.emitUpdate(self.displayUpdater)
                        
                        # execute pump strokes
                        if ( self.state.nPumpStrokes > 0 ):
                            logging.info("Ready to pump: " + xstr(self.state))
                            self.state.pumpEvent.set();
                        
                        logging.debug(xstr(self.state))
                        
                    except Exception,e:
                        logging.critical(str(e))
                elif report.get("class") == "SKY":
                    try:
                        self.state.gpsState.updateFromSKY(report)
                    except Exception,e:
                        logging.critical(str(e))
                
        except StopIteration:
            logging.fatal("GPSD has terminated")
        except Exception,e:
            logging.fatal("Unknown error: " + str(e))
            
        self.app.exit(1)
        logging.warning("Exiting ControlThread")
        
        
class GPIOThread(threading.Thread):
    '''
    A thread for reading the crossMode switch and the rain sensor.
    '''

    def __init__(self, state):
        '''
        Constructor
        '''
        threading.Thread.__init__(self)
        self.state = state
        logging.info("Creating GPIOThread")
        
    def run(self):
        logging.info("Starting GPIOThread")
        
        rainSignal = 1.0
        
        # In order to avoid oxidation on the rain sensor
        # we have to drive it with alternating current.
        #
        # Measurements will only be taken with PolarityStraight,
        # which is defined as
        #     rainSensorOut1 = 0
        #     rainSensorOut2 = 1
        isRainSensorPolarityStraight = False
        
        while (self.state.OK):
            addSleep = 0.0
            
            # handle the crossModeSwich including de-bounce
            if ( GPIO.input(pins.crossModeSwitch) == 0 ):
                time.sleep(bounceInterval)
                addSleep += bounceInterval
                if ( GPIO.input(pins.crossModeSwitch) == 0 ):
                    self.state.setCrossMode(True)
                else:
                    logging.debug("CrossMode bounce on -> off " + xstr(self.state))
            else:
                time.sleep(bounceInterval)
                addSleep += bounceInterval
                if ( GPIO.input(pins.crossModeSwitch) == 1 ):
                    self.state.setCrossMode(False)
                else:
                    logging.debug("CrossMode bounce off -> on " + xstr(self.state))
                    
            # Now check the rain sensor
            # we only update the rainSignal when we have straight polarity
            if ( isRainSensorPolarityStraight ):
                rainSignal = rainSignalAlpha * rainSignal + (1.0 - rainSignalAlpha) * GPIO.input(pins.rainSensor)
                #logging.info("Rainsignal: " + xstr(rainSignal))
                if ( rainSignal < rainSignalEntryLimitRain ):
                    if ( not self.state.rainMode ):
                        self.state.setRainMode(True)
                        logging.info("Detected rain mode on, signal " + xstr(rainSignal) + ", " + xstr(self.state))
                        GPIO.output(pins.rainModeLED, GPIO.HIGH)
                    
                elif ( rainSignal > rainSignalEntryLimitDry ):
                    if ( self.state.rainMode ):
                        self.state.setRainMode(False)
                        logging.info("Detected rain mode off, signal " + xstr(rainSignal) + ", " + xstr(self.state))
                        GPIO.output(pins.rainModeLED, GPIO.LOW)
                        
                # now reverse polarity
                GPIO.output(pins.rainSensorOut2, GPIO.LOW)
                GPIO.output(pins.rainSensorOut1, GPIO.HIGH)
                isRainSensorPolarityStraight = False
            else:
                # make polarity straight
                GPIO.output(pins.rainSensorOut1, GPIO.LOW)
                GPIO.output(pins.rainSensorOut2, GPIO.HIGH)
                isRainSensorPolarityStraight = True
            
            # now sleep at least 10ms
            sleepTime = max(GPIOInterval - addSleep, 0.01)
            time.sleep(sleepTime)
                        
        logging.warning("Exiting GPIOThread")

        
class TemperatureAndLogThread(threading.Thread):
    '''
    A thread for reading the temperature sensor,
    writing distance and XtraModeStrokes to the stats file
    and logging the state periodically
    '''

    def __init__(self, state):
        '''
        Constructor
        '''
        threading.Thread.__init__(self)
        self.state = state
        logging.info("Creating TemperatureAndLogThread")
        
    def run(self):
        logging.info("Starting TemperatureAndLogThread")
        
        i = 0
        while (self.state.OK):
            self.state.updateTemperature()
            
            # should be every minute
            if i % 3 == 0:
                writeStateToFile(self.state)
                
            logging.info(str(self.state))
            
            i = i+1
            time.sleep(sleepTimeTemperature)
            
        logging.warning("Exiting TemperatureAndLogThread")        

def writeStateToFile(state):
    '''
    Writing oilDistane - lastPump, xtraModePumpStrokes
    and a checksum to statFile. 
    '''
    if statFile is not None:
        try:
            dist = float(state.oilDistance - state.lastPump)
            strokes = state.xtraModePumpStrokes
            checksum = hash(str(dist + strokes))
            
            with open(statFile, 'w') as f:
                f.write(str(dist) + '\n' + str(strokes) + '\n' + str(checksum))
                f.flush
                
        except Exception,e:
            logging.warn("Unexpected error while writing state to file " + xstr(statFile) + ". Exception: " + xstr(e))
            


class TestThread(threading.Thread):
    '''
    Only for test purposes...
    '''
    
    def __init__(self, state):
        threading.Thread.__init__(self)
        logging.info("Creating TestThread")
        self.state = state
    
    def run(self):
        interval = 60
        # after x min set rain mode
        time.sleep(interval)
        logging.info("TestThread: rainMode on")
        self.state.setRainMode(True)
        
        # after x min turn rain mode off
        time.sleep(interval)
        logging.info("TestThread: rainMode off")
        self.state.setRainMode(False);
        
        # after x min set cross mode
        time.sleep(interval)
        logging.info("TestThread: crossMode on")
        self.state.setCrossMode(True);
        
        # after x min turn cross mode off
        time.sleep(10)
        logging.info("TestThread: crossMode off")
        self.state.setCrossMode(False);
        
